/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

/**
 *
 * @author Bruno Vicente
 */
public abstract class Servidor extends UnicastRemoteObject implements Gerenciador {

    /**
     * Colocar nome e ip
     */
    private HashMap<String, String> roteadoresNameIp;
    private HashMap<String, String> hostNameIP;
    private ArrayList<String> nomeRoteadores;
    /**
     * Representa o Grafo
     * Dentro dela vai possuir o peso
     */
    private String[][] rede;
    private HashMap<String, String> hostGatway;

    /**
     * Obtem a 
     * @param hostGatway 
     */
    public void setHostGatway(HashMap<String, String> hostGatway) {
        this.hostGatway = hostGatway;
    }

    /**
     * Cria uma instancia do servidor de redes.
     * @throws RemoteException 
     */
    Servidor() throws RemoteException {
        roteadoresNameIp = new HashMap<String, String>();
        hostNameIP = new HashMap<String, String>();
    }

    /**
     * Vai receber a tabela de roteadores.
     * 
     * @param rede 
     */
    public void setRede(String[][] rede) {
        this.rede = rede;
    }

    /**
     * Recebe um roteador da rede
     * @param ip
     */
    @Override
    public String receber_roteador(String ip) throws RemoteException {
        String nome = "";
        do {
            nome = JOptionPane.showInputDialog("Nome do Roteador de " + ip);
            //analisa para não aceitar roteadores de mesmo nome
        } while (nome.equals("") || roteadoresNameIp.containsKey(nome));
        roteadoresNameIp.put(nome, ip);
        redesenharTableRoteador(nome);
        return nome;
    }

    /**
     * Recebe um host da rede
     * @return o nome do Host
     */
    @Override
    public String receber_host(String ip) throws RemoteException {
        Integer size = hostNameIP.size();
        hostNameIP.put(size.toString(), ip);
        redesenharTableHost(size.toString());
        return size.toString();
    }

    /**
     * obtem o Ipv4 da maquina
     * @return numero do ip da maquina
     */
    public static String getIp() {
        InetAddress address = null;
        try {
            address = Inet4Address.getLocalHost();
        } catch (UnknownHostException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        return address.getHostAddress();
    }

    /**
     * Conectará a um host qualquer na rede.
     * @param name nome do host que se pretende conectar
     * @param Ip do host que se pretende conectar
     * @return host jah conectado
     */
    public Host conectarHost(String name, String Ip) {
        System.out.print("Conectar ao Host: ");
        System.out.println("rmi://" + Ip + "/" + name);
        try {
            Host host;
            host = (Host) Naming.lookup("rmi://" + Ip + "/" + name);
            return host;
        } catch (NotBoundException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MalformedURLException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (RemoteException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    /**Conecta Host ao seu Roteador gateway
     * 
     * @param name  do roteaddor que se pretende conectar
     * @param ipRoteador que se pretende adicionar na rede
     * @return roteador jah conectado podendo enviar informações para este roteador.
     */
    public Roteador conectarRoteador(String name, String ipRoteador) {
        Roteador roteador = null;
        try {
            System.out.println("Conectando ao Roteador:" + name);
            System.out.println(ipRoteador);
            roteador = (Roteador) Naming.lookup("rmi://" + ipRoteador + "/" + name);
            System.out.println("Roteador Conectado");
        } catch (NotBoundException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MalformedURLException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        } catch (RemoteException ex) {
            Logger.getLogger(HostImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        return roteador;
    }

    abstract void redesenharTableRoteador(String name);

    abstract void redesenharTableHost(String name);

    /**
     * Envia informação da Rede para todos os Nos.
     * @param alterar  se true ira alterar a tabela dos roteadores
     * caso contrario estará passando a rede pela primeira vez aos roteadores.
     */
    public void enviarRede(boolean alterar, boolean bellman) {
        String[] nameRoteadores = getNomeRoteadores();
        for (int i = 1; i < rede.length; i++) {
            String name = rede[i][0];
            Roteador conectarRoteador = conectarRoteador(name, roteadoresNameIp.get(name));
            try {
                if (alterar) {
                    conectarRoteador.alterarRede();
                }
                conectarRoteador.setRede(rede);
                Integer[] pesos = getPesos(name);
                conectarRoteador.criarNo(pesos, nameRoteadores);
                conectarRoteador.alterarAlgoritmo(bellman);
                ArrayList<String> hosts = getHosts(name);
                for (String host : hosts) {
                    Host hostConectado = conectarHost(host, hostNameIP.get(host));
                    hostConectado.setGatway(name, roteadoresNameIp.get(name));
                    conectarRoteador.addHost(host, hostNameIP.get(host));
                }
                for (int j = 1; j < rede.length; j++) {
                    if (rede[i][j] != null && !rede[i][j].equals("")) {
                        System.out.println("Rede:" + rede[i][j]);
                        conectarRoteador.addVizinhos(rede[0][j], roteadoresNameIp.get(rede[0][j]));
                    }
                }

            } catch (RemoteException ex) {
                Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        configurarBell();
    }

    /**
     * vai adquirir todos os hosts que um Roteador esta ligado
     */
    private ArrayList<String> getHosts(String nameRoteador) {
        ArrayList<String> hosts = new ArrayList<String>();
        Set<String> keySet = hostGatway.keySet();
        for (String host : keySet) {
            String roteador = hostGatway.get(host);
            if (nameRoteador.equals(roteador)) {
                hosts.add(host);
            }
        }
        return hosts;
    }

    /**
     * Isto será feito para saber qual caminho percorrido para enviar um pacote
     * @param name do roteador que enviou informação
     * @param destino nome do nó para o qual foi enviado o pacote
     * @param roteador  True para informar que pacote foi enviado para roteador
     *False para informar que foi enviado para host da rede.
     * @throws RemoteException
     */
    @Override
    public void receberInformação(String name, String destino, Boolean roteador) throws RemoteException {
        if (roteador) {
            JOptionPane.showMessageDialog(null, "Pacote está no Roteador " + name + " irá para roteador " + destino);
        } else {
            JOptionPane.showMessageDialog(null, "Pacote está no Roteador " + name + " este pacote é para minha subrede.");
        }
    }

    /**
     * Para informar que pacote jah rodou demais pela rede. E seu TTL estorou.
     * @param name do roteador em que o pacote estorou.
     * @throws RemoteException
     */
    @Override
    public void estourouTtl(String name) throws RemoteException {
        JOptionPane.showMessageDialog(null, "Pacote ja rodou demais.Séra descartado no roteador " + name);
    }

    /**
     * Limpa as telas de todos os roteadores.
     * @throws RemoteException
     */
    public void limparrede() throws RemoteException {
        for (Iterator<String> it = roteadoresNameIp.keySet().iterator(); it.hasNext();) {
            String nameRoteador = it.next();
            Roteador conectarRoteador = conectarRoteador(nameRoteador, roteadoresNameIp.get(nameRoteador));
            conectarRoteador.limparTelas();
        }
    }

    /**
     * Limpa as telas de todos os roteadores.
     * @throws RemoteException
     */
    public void alterarAlgoritmo(boolean bellman) throws RemoteException {
        for (Iterator<String> it = roteadoresNameIp.keySet().iterator(); it.hasNext();) {
            String nameRoteador = it.next();
            Roteador conectarRoteador = conectarRoteador(nameRoteador, roteadoresNameIp.get(nameRoteador));
            conectarRoteador.alterarAlgoritmo(bellman);
        }
    }

    private void configurarBell() {

        for (int i = 1; i < rede.length; i++) {
            try {
                String name = rede[i][0];
                System.out.println("Iniciando "+name );
                Roteador conectarRoteador = conectarRoteador(name, roteadoresNameIp.get(name));
                conectarRoteador.executaBellman();
            } catch (RemoteException ex) {
                Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private String[] getNomeRoteadores() {
        String nome[] = new String[rede.length - 1];
        ArrayList<String> nomeRoteadores = new ArrayList<String>();
        for (int i = 1; i < rede.length; i++) {
            nome[i - 1] = (rede[i][0]);
            nomeRoteadores.add(rede[i][0]);
        }
        this.nomeRoteadores = nomeRoteadores;
        return nome;
    }

    private Integer[] getPesos(String nome) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        for (int i = 1; i < rede.length; i++) {
            if (nomeRoteadores.indexOf(nome) + 1 == i) {
                arrayList.add(0);
            } else {
                String strings = rede[nomeRoteadores.indexOf(nome) + 1][i];
                if (strings == null) {
                    arrayList.add(DV.INFINITO);
                } else {
                    arrayList.add(Integer.parseInt(strings));
                }
            }
        }
        Integer[] pesos = new Integer[arrayList.size()];
        for (int i = 0; i < arrayList.size(); i++) {
            pesos[i] = arrayList.get(i);
        }
        return pesos;
    }
}
